﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenTK.Graphics.OpenGL;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

namespace Utils
{
    public class TextureUtils
    {
        public static int GROUND { get; private set; }
        public static int GROUND2 { get; private set; }
        public static int RAMP { get; private set; }
        public static int RAMP2 { get; private set; }
        public static void Initializer()
        {
            GL.Enable(EnableCap.Texture2D);
            GL.Hint(HintTarget.PerspectiveCorrectionHint, HintMode.Nicest);
            GROUND = loadTexture("ground.png");
            RAMP = loadTexture("ramp.png");
            GROUND2 = loadTexture("ground2.png");
            RAMP2 = loadTexture("ramp2.png");
        }
        public static int loadTexture(String filename)
        {
            Bitmap bitmap = new Bitmap(Directory.GetCurrentDirectory() + "\\..\\..\\..\\Model\\Texture\\" + filename);

            BitmapData data = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
                ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            int texture = GL.GenTexture(); ;

            GL.BindTexture(TextureTarget.Texture2D, texture);
            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, data.Width, data.Height, 0,
                OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);

            bitmap.UnlockBits(data);
            

            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
            return texture;
        }
    }
    [Serializable]
    public class Point3D
    {
        public double X { get; set; }
        public double Y { get; set; }
        public double Z { get; set; }

        public static Point3D Empty()
        {
            return new Point3D(0, 0, 0);
        }

        public Point3D(double x, double y)
        {
            X = x;
            Y = y;
            Z = 0;
        }

        public Point3D(double x, double y, double z)
        {
            X = x;
            Y = y;
            Z = z;
        }

        public bool IsEmpty()
        {
            return X == 0 && Y == 0 && Z == 0;
        }

        public override string ToString()
        {
            return string.Format("X: {0}, Y: {1}, Z: {2}", X, Y, Z);
        }
    }

    [Serializable]
    public class Angle3D
    {
        private double _angle;
        public double AngleRadians
        {
            get { return _angle; }
            set
            {
                _angle = value;
                CosAngle = Math.Cos(_angle);
                SinAngle = Math.Sin(_angle);
            }
        }

        public double AngleDegrees
        {
            get { return _angle * (180 / Math.PI); }
            set
            {
                _angle = value * (Math.PI / 180);
                CosAngle = Math.Cos(_angle);
                SinAngle = Math.Sin(_angle);
            }
        }

        public double CosAngle { get; private set; }
        public double SinAngle { get; private set; }

        public Angle3D()
        {
            AngleRadians = 0;
        }
    }

    public class BoundingBox
    {
        private bool _complex;

        public Point3D Center;
        public double Height;
        public double Width;
        public double Depth;
        public List<Point3D> Points;

        public double Right { get { return Center.X + (Width / 2); } }
        public double Left { get { return Center.X - (Width / 2); } }
        public double Top { get { return Center.Y + (Height / 2); } }
        public double Bottom { get { return Center.Y - (Height / 2); } }
        public double Back { get { return Center.Z + (Depth / 2); } }
        public double Front { get { return Center.Z - (Depth / 2); } }

        public BoundingBox(List<Point3D> points, Point3D center, double height, double width, double depth)
            : this(center, height, width, depth)
        {
            Points = points;
            _complex = true;
        }

        public BoundingBox(Point3D center, double height, double width, double depth)
        {
            Center = center;
            Height = height;
            Width = width;
            Depth = depth;
        }

        public BoundingBox(Point3D max, Point3D min)
        {
            Height = max.Y - min.Y;
            Width = max.X - min.X;
            Depth = max.Z - min.Z;

            Center = new Point3D(max.X - (Width / 2), max.Y - (Height / 2), max.Z - (Depth / 2));
        }

        public void Intersection(ref BoundingBox box)
        {
            double lr = Left - box.Right;
            double rl = box.Left - Right;
            double bt = Bottom - box.Top;
            double tb = box.Bottom - Top;
            double fb = Front - box.Back;
            double bf = box.Front - Back;

            if (lr > 0 || rl > 0 || bt > 0 || tb > 0 || bf > 0 || fb > 0)
                return;

            double max = Math.Max(lr, Math.Max(rl, Math.Max(bt, Math.Max(tb, Math.Max(bf, fb)))));

            if (_complex)
            {
                if (ComplexIntersection(ref box))
                    return;
            }

            if (max == lr)
                box.Center.X += max;
            else if (max == rl)
                box.Center.X -= max;
            else if (max == bt)
                box.Center.Y += max;
            else if (max == tb)
                box.Center.Y -= max;
            else if (max == fb)
                box.Center.Z += max;
            else if (max == bf)
                box.Center.Z -= max;
        }

        private bool ComplexIntersection(ref BoundingBox box)
        {
            bool xSlope;
            double angle;

            if (Points[0].X != Points[1].X)
            {
                xSlope = Points[0].Y != Points[1].Y;
                angle = xSlope ? GetAngle(Points[0], Points[1], true) : GetAngle(Points[1], Points[2], false);
                return xSlope ? TestCollision(Points[0], Points[1], true, angle, ref box) : TestCollision(Points[1], Points[2], false, angle, ref box);
            }

            xSlope = Points[0].Y == Points[1].Y;
            angle = !xSlope ? GetAngle(Points[0], Points[1], false) : GetAngle(Points[1], Points[2], true);
            return !xSlope ? TestCollision(Points[0], Points[1], false, angle, ref box) : TestCollision(Points[1], Points[2], true, angle, ref box);
        }

        private static bool TestCollision(Point3D p1, Point3D p2, bool xSlope, double angle, ref BoundingBox box)
        {
            var p1C = xSlope ? p1.X : p1.Z;
            var p2C = xSlope ? p2.X : p2.Z;
            var p1Y = p1.Y;
            var p2Y = p2.Y;
            var boxN = xSlope ? box.Left : box.Front;
            var boxP = xSlope ? box.Right : box.Back;

            if (p1C > p2C && angle > 0)
            {
                double dy = box.Bottom - p2Y;
                double adj = dy / Math.Tan(angle);
                p2C += adj;
                //  p2.X is on the ground; left-top, right-bottom
                if (Math.Abs(p2C - boxN) > Math.Abs(p2C - boxP))
                    //  right-bottom
                    return RightBottom(p2, angle, xSlope, ref box);

                //  left-top
                return LeftTop(p2, angle, xSlope, ref box);
            }
            if (p1C < p2C && angle < 0)
            {
                double dy = box.Bottom - p2Y;
                double adj = dy / Math.Tan(angle);
                p2C += adj;
                //  p2.X is on the ground; right-top, left-bottom
                if (Math.Abs(p2C - boxN) > Math.Abs(p2C - boxP))
                    //  right-top
                    return RightTop(p2, angle, xSlope, ref box);

                //  left-bottom
                return LeftBottom(p2, angle, xSlope, ref box);
            }
            if (p1C < p2C && angle > 0)
            {
                double dy = box.Bottom - p1Y;
                double adj = dy / Math.Tan(angle);
                p1C += adj;
                //  p1.X is on the ground; left-top, right-bottom
                if (Math.Abs(p1C - boxN) > Math.Abs(p1C - boxP))
                    //  right-bottom
                    return RightBottom(p1, angle, xSlope, ref box);

                //  left-top
                return LeftTop(p1, angle, xSlope, ref box);
            }
            if (p1C > p2C && angle < 0)
            {
                double dy = box.Bottom - p1Y;
                double adj = dy / Math.Tan(angle);
                p1C += adj;
                //  p1.X is on the ground; right-top, left-bottom    )
                if (Math.Abs(p1C - boxN) > Math.Abs(p1C - boxP))
                    //  right-top
                    return RightTop(p2, angle, xSlope, ref box);

                //  left-bottom
                return LeftBottom(p1, angle, xSlope, ref box);
            }
            return true;
        }

        private static bool LeftBottom(Point3D point, double angle, bool xSlope, ref BoundingBox box)
        {
            if (xSlope)
            {
                double adj = Math.Abs(point.X - box.Left);
                double ops = Math.Abs(Math.Tan(angle) * adj);
                ops += point.Y + (box.Height / 2);
                //if (Math.Abs(ops - box.Bottom) > 0.3)
                //    return false;
                box.Center.Y = ops;
                return true;
            }

            //  front bottom

            double adj2 = Math.Abs(point.Z - box.Front);
            double ops2 = Math.Abs(Math.Tan(angle) * adj2);
            ops2 += point.Y + (box.Height / 2);
            //if (Math.Abs(ops2 - box.Bottom) > 0.31)
            //    return false;
            box.Center.Y = ops2;
            return true;

        }

        private static bool RightTop(Point3D point, double angle, bool xSlope, ref BoundingBox box)
        {
            if (xSlope)
            {
                double adj = Math.Abs(point.X - box.Right);
                double ops = Math.Tan(angle) * adj;
                ops += point.Y;
                if (ops < box.Top)
                {
                    if (box.Top - ops > 0.3)
                        return false;
                    box.Center.X -= 0.3;
                }
                return true;
            }

            //  back top

            double adj2 = Math.Abs(point.Z - box.Back);
            double ops2 = Math.Tan(angle) * adj2;
            ops2 += point.Y;
            if (ops2 < box.Top)
            {
                if (box.Top - ops2 > .31)
                    return false;
                box.Center.Z -= 0.3;
            }
            return true;
        }

        private static bool LeftTop(Point3D point, double angle, bool xSlope, ref BoundingBox box)
        {
            if (xSlope)
            {
                double adj = Math.Abs(point.X - box.Left);
                double ops = Math.Tan(angle) * adj;
                ops += point.Y;
                if (ops < box.Top)
                {
                    if (Math.Abs(ops - box.Top) > 0.3)
                        return false;
                    box.Center.X += 0.3;
                }
                return true;
            }

            //  front top
            double adj2 = Math.Abs(point.Z - box.Front);
            double ops2 = Math.Tan(angle) * adj2;
            ops2 += point.Y;
            if (ops2 < box.Top)
            {
                if (Math.Abs(ops2 - box.Top) > 0.3)
                    return false;
                box.Center.Z += 0.3;
            }
            return true;
        }

        private static bool RightBottom(Point3D point, double angle, bool xSlope, ref BoundingBox box)
        {
            if (xSlope)
            {
                double adj = Math.Abs(point.X - box.Right);
                double ops = Math.Tan(angle) * adj;
                ops += point.Y + (box.Height / 2);
                //if (Math.Abs(ops - box.Bottom) > 0.3)
                //    return false;
                box.Center.Y = ops;
                return true;
            }

            //  back bottom
            double adj2 = Math.Abs(point.Z - box.Back);
            double ops2 = Math.Tan(angle) * adj2;
            ops2 += point.Y + (box.Height / 2);
            //if (Math.Abs(ops2 - box.Bottom) > 0.3)
            //    return false;
            box.Center.Y = ops2;
            return true;
        }

        private static double GetAngle(Point3D p1, Point3D p2, bool xSlope)
        {
            if (xSlope)
            {
                if (p1.X > p2.X)
                {
                    return Math.Atan((p2.Y - p1.Y) / (p2.X - p1.X));
                }
                return Math.Atan((p1.Y - p2.Y) / (p1.X - p2.X));
            }
            if (p1.Z > p2.Z)
            {
                return Math.Atan((p2.Y - p1.Y) / (p2.Z - p1.Z));
            }
            return Math.Atan((p1.Y - p2.Y) / (p1.Z - p2.Z));
        }

        public void DrawBox()
        {
            GL.Begin(BeginMode.Lines);

            const double offset = 0.2;
            if (!_complex)
            {
                GL.Vertex3(Left - offset, Bottom - offset, Front - offset);
                GL.Vertex3(Right + offset, Bottom - offset, Front - offset);

                GL.Vertex3(Right + offset, Bottom - offset, Front - offset);
                GL.Vertex3(Right + offset, Top + offset, Front - offset);

                GL.Vertex3(Right + offset, Top + offset, Front - offset);
                GL.Vertex3(Left - offset, Top + offset, Front - offset);

                GL.Vertex3(Left - offset, Top + offset, Front - offset);
                GL.Vertex3(Left - offset, Bottom - offset, Front - offset);


                GL.Vertex3(Left - offset, Bottom - offset, Back + offset);
                GL.Vertex3(Right + offset, Bottom - offset, Back + offset);

                GL.Vertex3(Right + offset, Bottom - offset, Back + offset);
                GL.Vertex3(Right + offset, Top + offset, Back + offset);

                GL.Vertex3(Right + offset, Top + offset, Back + offset);
                GL.Vertex3(Left - offset, Top + offset, Back + offset);

                GL.Vertex3(Left - offset, Top + offset, Back + offset);
                GL.Vertex3(Left - offset, Bottom - offset, Back + offset);


                GL.Vertex3(Left - offset, Bottom - offset, Back + offset);
                GL.Vertex3(Left - offset, Bottom - offset, Front - offset);

                GL.Vertex3(Right + offset, Bottom - offset, Back + offset);
                GL.Vertex3(Right + offset, Bottom - offset, Front - offset);

                GL.Vertex3(Left - offset, Top + offset, Back + offset);
                GL.Vertex3(Left - offset, Top + offset, Front - offset);

                GL.Vertex3(Right + offset, Top + offset, Back + offset);
                GL.Vertex3(Right + offset, Top + offset, Front - offset);
            }

            GL.End();
        }
    }
}
